/*
 * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.engine.test.history;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.history.HistoricVariableUpdate;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.persistence.entity.HistoricVariableInstanceEntity;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.engine.test.Deployment;

public class HistoricVariableInstanceTest extends PluggableActivitiTestCase {

    @Deployment(
        resources = {
            "org/activiti/examples/bpmn/callactivity/orderProcess.bpmn20.xml",
            "org/activiti/examples/bpmn/callactivity/checkCreditProcess.bpmn20.xml",
        }
    )
    public void testOrderProcessWithCallActivity() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            // After the process has started, the 'verify credit history' task should be active
            ProcessInstance pi = runtimeService.startProcessInstanceByKey("orderProcess");
            TaskQuery taskQuery = taskService.createTaskQuery();
            Task verifyCreditTask = taskQuery.singleResult();
            assertThat(verifyCreditTask.getName()).isEqualTo("Verify credit history");

            // Verify with Query API
            ProcessInstance subProcessInstance = runtimeService
                .createProcessInstanceQuery()
                .superProcessInstanceId(pi.getId())
                .singleResult();
            assertThat(subProcessInstance).isNotNull();
            assertThat(
                runtimeService
                    .createProcessInstanceQuery()
                    .subProcessInstanceId(subProcessInstance.getId())
                    .singleResult()
                    .getId()
            ).isEqualTo(pi.getId());

            // Completing the task with approval, will end the subprocess and continue the original process
            taskService.complete(verifyCreditTask.getId(), singletonMap("creditApproved", true));
            Task prepareAndShipTask = taskQuery.singleResult();
            assertThat(prepareAndShipTask.getName()).isEqualTo("Prepare and Ship");
        }
    }

    @Deployment
    public void testSimple() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc");
            TaskQuery taskQuery = taskService.createTaskQuery();
            Task userTask = taskQuery.singleResult();
            assertThat(userTask.getName()).isEqualTo("userTask1");

            taskService.complete(userTask.getId(), singletonMap("myVar", "test789"));

            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().list();
            assertThat(variables).hasSize(1);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getTextValue()).isEqualTo("test456");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(5);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(3);
        }
    }

    @Deployment
    public void testSimpleNoWaitState() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc");
            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().list();
            assertThat(variables).hasSize(1);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getTextValue()).isEqualTo("test456");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(4);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(2);
        }
    }

    @Deployment
    public void testParallel() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc");
            TaskQuery taskQuery = taskService.createTaskQuery();
            Task userTask = taskQuery.singleResult();
            assertThat(userTask.getName()).isEqualTo("userTask1");

            taskService.complete(userTask.getId(), singletonMap("myVar", "test789"));

            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService
                .createHistoricVariableInstanceQuery()
                .orderByVariableName()
                .asc()
                .list();
            assertThat(variables).hasSize(2);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getName()).isEqualTo("myVar");
            assertThat(historicVariable.getTextValue()).isEqualTo("test789");

            HistoricVariableInstanceEntity historicVariable1 = (HistoricVariableInstanceEntity) variables.get(1);
            assertThat(historicVariable1.getName()).isEqualTo("myVar1");
            assertThat(historicVariable1.getTextValue()).isEqualTo("test456");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(8);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(5);
        }
    }

    @Deployment
    public void testParallelNoWaitState() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc");
            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().list();
            assertThat(variables).hasSize(1);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getTextValue()).isEqualTo("test456");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(7);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(2);
        }
    }

    @Deployment
    public void testTwoSubProcessInParallelWithinSubProcess() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
                "twoSubProcessInParallelWithinSubProcess"
            );
            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService
                .createHistoricVariableInstanceQuery()
                .orderByVariableName()
                .asc()
                .list();
            assertThat(variables).hasSize(2);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getName()).isEqualTo("myVar");
            assertThat(historicVariable.getTextValue()).isEqualTo("test101112");

            HistoricVariableInstanceEntity historicVariable1 = (HistoricVariableInstanceEntity) variables.get(1);
            assertThat(historicVariable1.getName()).isEqualTo("myVar1");
            assertThat(historicVariable1.getTextValue()).isEqualTo("test789");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(18);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(7);
        }
    }

    @Deployment(
        resources = {
            "org/activiti/engine/test/history/HistoricVariableInstanceTest.testCallSimpleSubProcess.bpmn20.xml",
            "org/activiti/engine/test/history/simpleSubProcess.bpmn20.xml",
        }
    )
    public void testHistoricVariableInstanceQuery() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("callSimpleSubProcess");
            assertProcessEnded(processInstance.getId());

            assertThat(historyService.createHistoricVariableInstanceQuery().count()).isEqualTo(4);
            assertThat(historyService.createHistoricVariableInstanceQuery().list()).hasSize(4);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().orderByProcessInstanceId().asc().count()
            ).isEqualTo(4);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().orderByProcessInstanceId().asc().list()
            ).hasSize(4);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().orderByVariableName().asc().count()
            ).isEqualTo(4);
            assertThat(historyService.createHistoricVariableInstanceQuery().orderByVariableName().asc().list()).hasSize(
                4
            );

            assertThat(
                historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId()).count()
            ).isEqualTo(2);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId()).list()
            ).hasSize(2);
            assertThat(historyService.createHistoricVariableInstanceQuery().variableName("myVar").count()).isEqualTo(2);
            assertThat(historyService.createHistoricVariableInstanceQuery().variableName("myVar").list()).hasSize(2);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableNameLike("myVar1").count()
            ).isEqualTo(2);
            assertThat(historyService.createHistoricVariableInstanceQuery().variableNameLike("myVar1").list()).hasSize(
                2
            );

            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().list();
            assertThat(variables).hasSize(4);

            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar", "test123").count()
            ).isEqualTo(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar", "test123").list()
            ).hasSize(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar1", "test456").count()
            ).isEqualTo(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar1", "test456").list()
            ).hasSize(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar", "test666").count()
            ).isEqualTo(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar", "test666").list()
            ).hasSize(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar1", "test666").count()
            ).isEqualTo(1);
            assertThat(
                historyService.createHistoricVariableInstanceQuery().variableValueEquals("myVar1", "test666").list()
            ).hasSize(1);

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(8);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(5);
        }
    }

    public void testHistoricVariableQuery2() {
        deployTwoTasksTestProcess();

        // Generate data
        Map<String, Object> startVars = new HashMap<String, Object>();
        startVars.put("startVar", "hello");
        String processInstanceId = runtimeService.startProcessInstanceByKey("twoTasksProcess", startVars).getId();
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        for (int i = 0; i < tasks.size(); i++) {
            runtimeService.setVariableLocal(tasks.get(i).getExecutionId(), "executionVar" + i, i);
            taskService.setVariableLocal(tasks.get(i).getId(), "taskVar" + i, i);
        }

        // Verify historic variable instance queries
        List<HistoricVariableInstance> historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId)
            .orderByVariableName()
            .asc()
            .list();
        assertThat(historicVariableInstances).hasSize(5);

        List<String> expectedVariableNames = asList(
            "executionVar0",
            "executionVar1",
            "startVar",
            "taskVar0",
            "taskVar1"
        );
        for (int i = 0; i < expectedVariableNames.size(); i++) {
            assertThat(historicVariableInstances.get(i).getVariableName()).isEqualTo(expectedVariableNames.get(i));
        }

        // by execution id
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionId(tasks.get(0).getExecutionId())
            .orderByVariableName()
            .asc()
            .list();
        assertThat(historicVariableInstances).hasSize(2);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("executionVar0");
        assertThat(historicVariableInstances.get(1).getVariableName()).isEqualTo("taskVar0");
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionId(tasks.get(1).getExecutionId())
            .orderByVariableName()
            .asc()
            .list();
        assertThat(historicVariableInstances).hasSize(2);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("executionVar1");
        assertThat(historicVariableInstances.get(1).getVariableName()).isEqualTo("taskVar1");

        // By process instance id and execution id
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId)
            .executionId(tasks.get(0).getExecutionId())
            .orderByVariableName()
            .asc()
            .list();
        assertThat(historicVariableInstances).hasSize(2);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("executionVar0");
        assertThat(historicVariableInstances.get(1).getVariableName()).isEqualTo("taskVar0");
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId)
            .executionId(tasks.get(1).getExecutionId())
            .orderByVariableName()
            .asc()
            .list();
        assertThat(historicVariableInstances).hasSize(2);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("executionVar1");
        assertThat(historicVariableInstances.get(1).getVariableName()).isEqualTo("taskVar1");

        // By task id
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .taskId(tasks.get(0).getId())
            .list();
        assertThat(historicVariableInstances).hasSize(1);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar0");
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .taskId(tasks.get(1).getId())
            .list();
        assertThat(historicVariableInstances).hasSize(1);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar1");

        // By task id and process instance id
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId)
            .taskId(tasks.get(0).getId())
            .list();
        assertThat(historicVariableInstances).hasSize(1);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar0");
        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .processInstanceId(processInstanceId)
            .taskId(tasks.get(1).getId())
            .list();
        assertThat(historicVariableInstances).hasSize(1);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar1");
    }

    public void testHistoricVariableQueryByExecutionIds() {
        deployTwoTasksTestProcess();

        Set<String> processInstanceIds = new HashSet<String>();
        Set<String> testProcessInstanceIds = new HashSet<String>();
        for (int i = 0; i < 3; i++) {
            // Generate data
            Map<String, Object> startVars = new HashMap<String, Object>();
            if (i == 1) {
                startVars.put("startVar2", "hello2");
            } else {
                startVars.put("startVar", "hello");
            }
            String processInstanceId = runtimeService.startProcessInstanceByKey("twoTasksProcess", startVars).getId();
            processInstanceIds.add(processInstanceId);
            if (i != 1) {
                testProcessInstanceIds.add(processInstanceId);
            }
        }

        assertThat(
            historyService.createHistoricVariableInstanceQuery().executionIds(testProcessInstanceIds).list().size()
        )
            .isEqualTo(
                (int) historyService.createHistoricVariableInstanceQuery().executionIds(testProcessInstanceIds).count()
            )
            .isEqualTo(2);

        List<HistoricVariableInstance> historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionIds(testProcessInstanceIds)
            .list();
        assertThat(historicVariableInstances)
            .hasSize(2)
            .extracting("name", "value")
            .containsExactly(tuple("startVar", "hello"), tuple("startVar", "hello"));

        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionIds(processInstanceIds)
            .list();

        assertThat(historicVariableInstances)
            .hasSize(3)
            .extracting("name", "value")
            .containsExactlyInAnyOrder(
                tuple("startVar", "hello"),
                tuple("startVar", "hello"),
                tuple("startVar2", "hello2")
            );
    }

    @Deployment(resources = { "org/activiti/engine/test/api/runtime/variableScope.bpmn20.xml" })
    public void testHistoricVariableQueryByExecutionIdsForScope() {
        Map<String, Object> processVars = new HashMap<String, Object>();
        processVars.put("processVar", "processVar");
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("variableScopeProcess", processVars);

        Set<String> executionIds = new HashSet<String>();
        List<Execution> executions = runtimeService
            .createExecutionQuery()
            .processInstanceId(processInstance.getId())
            .list();
        for (Execution execution : executions) {
            if (!processInstance.getId().equals(execution.getId())) {
                executionIds.add(execution.getId());
                runtimeService.setVariableLocal(execution.getId(), "executionVar", "executionVar");
            }
        }

        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
        for (Task task : tasks) {
            taskService.setVariableLocal(task.getId(), "taskVar", "taskVar");
        }

        Set<String> processInstanceIds = new HashSet<String>();
        processInstanceIds.add(processInstance.getId());
        List<HistoricVariableInstance> historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionIds(processInstanceIds)
            .list();
        assertThat(1).isEqualTo(historicVariableInstances.size());
        assertThat("processVar").isEqualTo(historicVariableInstances.get(0).getVariableName());
        assertThat("processVar").isEqualTo(historicVariableInstances.get(0).getValue());

        historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .executionIds(executionIds)
            .excludeTaskVariables()
            .list();
        assertThat(2).isEqualTo(historicVariableInstances.size());
        assertThat("executionVar").isEqualTo(historicVariableInstances.get(0).getVariableName());
        assertThat("executionVar").isEqualTo(historicVariableInstances.get(0).getValue());
        assertThat("executionVar").isEqualTo(historicVariableInstances.get(1).getVariableName());
        assertThat("executionVar").isEqualTo(historicVariableInstances.get(1).getValue());
    }

    public void testHistoricVariableQueryByTaskIds() {
        deployTwoTasksTestProcess();
        // Generate data
        String processInstanceId = runtimeService.startProcessInstanceByKey("twoTasksProcess").getId();
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        taskService.setVariableLocal(tasks.get(0).getId(), "taskVar1", "hello1");
        taskService.setVariableLocal(tasks.get(1).getId(), "taskVar2", "hello2");

        Set<String> taskIds = new HashSet<String>();
        taskIds.add(tasks.get(0).getId());
        taskIds.add(tasks.get(1).getId());
        List<HistoricVariableInstance> historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .taskIds(taskIds)
            .list();
        assertThat(historyService.createHistoricVariableInstanceQuery().taskIds(taskIds).count()).isEqualTo(2);
        assertThat(historicVariableInstances).hasSize(2);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar1");
        assertThat(historicVariableInstances.get(0).getValue()).isEqualTo("hello1");
        assertThat(historicVariableInstances.get(1).getVariableName()).isEqualTo("taskVar2");
        assertThat(historicVariableInstances.get(1).getValue()).isEqualTo("hello2");

        taskIds = new HashSet<String>();
        taskIds.add(tasks.get(0).getId());
        historicVariableInstances = historyService.createHistoricVariableInstanceQuery().taskIds(taskIds).list();
        assertThat(historyService.createHistoricVariableInstanceQuery().taskIds(taskIds).count()).isEqualTo(1);
        assertThat(historicVariableInstances).hasSize(1);
        assertThat(historicVariableInstances.get(0).getVariableName()).isEqualTo("taskVar1");
        assertThat(historicVariableInstances.get(0).getValue()).isEqualTo("hello1");
    }

    @Deployment(resources = { "org/activiti/engine/test/api/runtime/variableScope.bpmn20.xml" })
    public void testHistoricVariableQueryByTaskIdsForScope() {
        Map<String, Object> processVars = new HashMap<String, Object>();
        processVars.put("processVar", "processVar");
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("variableScopeProcess", processVars);

        Set<String> executionIds = new HashSet<String>();
        List<Execution> executions = runtimeService
            .createExecutionQuery()
            .processInstanceId(processInstance.getId())
            .list();
        for (Execution execution : executions) {
            if (!processInstance.getId().equals(execution.getId())) {
                executionIds.add(execution.getId());
                runtimeService.setVariableLocal(execution.getId(), "executionVar", "executionVar");
            }
        }

        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
        Set<String> taskIds = new HashSet<String>();
        for (Task task : tasks) {
            taskService.setVariableLocal(task.getId(), "taskVar", "taskVar");
            taskIds.add(task.getId());
        }

        List<HistoricVariableInstance> historicVariableInstances = historyService
            .createHistoricVariableInstanceQuery()
            .taskIds(taskIds)
            .list();
        assertThat(2).isEqualTo(historicVariableInstances.size());
        assertThat("taskVar").isEqualTo(historicVariableInstances.get(0).getVariableName());
        assertThat("taskVar").isEqualTo(historicVariableInstances.get(0).getValue());
        assertThat("taskVar").isEqualTo(historicVariableInstances.get(1).getVariableName());
        assertThat("taskVar").isEqualTo(historicVariableInstances.get(1).getValue());
    }

    @Deployment(resources = { "org/activiti/engine/test/api/oneTaskProcess.bpmn20.xml" })
    public void testHistoricProcessVariableOnDeletion() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            HashMap<String, Object> variables = new HashMap<String, Object>();
            variables.put("testVar", "Hallo Christian");
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", variables);
            runtimeService.deleteProcessInstance(processInstance.getId(), "deleted");
            assertProcessEnded(processInstance.getId());

            // check that process variable is set even if the process is canceled and not ended normally
            assertThat(
                historyService
                    .createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstance.getId())
                    .variableValueEquals("testVar", "Hallo Christian")
                    .count()
            ).isEqualTo(1);
        }
    }

    @Deployment(
        resources = {
            "org/activiti/standalone/history/FullHistoryTest.testVariableUpdatesAreLinkedToActivity.bpmn20.xml",
        }
    )
    public void testVariableUpdatesLinkedToActivity() throws Exception {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance pi = runtimeService.startProcessInstanceByKey("ProcessWithSubProcess");

            Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
            Map<String, Object> variables = new HashMap<String, Object>();
            variables.put("test", "1");
            taskService.complete(task.getId(), variables);

            // now we are in the subprocess
            task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
            variables.clear();
            variables.put("test", "2");
            taskService.complete(task.getId(), variables);

            // now we are ended
            assertProcessEnded(pi.getId());

            // check history
            List<HistoricDetail> updates = historyService.createHistoricDetailQuery().variableUpdates().list();
            assertThat(updates).hasSize(2);

            Map<String, HistoricVariableUpdate> updatesMap = new HashMap<String, HistoricVariableUpdate>();
            HistoricVariableUpdate update = (HistoricVariableUpdate) updates.get(0);
            updatesMap.put((String) update.getValue(), update);
            update = (HistoricVariableUpdate) updates.get(1);
            updatesMap.put((String) update.getValue(), update);

            HistoricVariableUpdate update1 = updatesMap.get("1");
            HistoricVariableUpdate update2 = updatesMap.get("2");

            assertThat(update1.getActivityInstanceId()).isNotNull();
            assertThat(update1.getExecutionId()).isNotNull();
            HistoricActivityInstance historicActivityInstance1 = historyService
                .createHistoricActivityInstanceQuery()
                .activityInstanceId(update1.getActivityInstanceId())
                .singleResult();
            assertThat(historicActivityInstance1.getActivityId()).isEqualTo("usertask1");

            // TODO https://activiti.atlassian.net/browse/ACT-1083
            assertThat(update2.getActivityInstanceId()).isNotNull();
            HistoricActivityInstance historicActivityInstance2 = historyService
                .createHistoricActivityInstanceQuery()
                .activityInstanceId(update2.getActivityInstanceId())
                .singleResult();
            assertThat(historicActivityInstance2.getActivityId()).isEqualTo("usertask2");

            /*
             * This is OK! The variable is set on the root execution, on a execution never run through the activity, where the process instances stands when calling the set Variable. But the ActivityId of
             * this flow node is used. So the execution id's doesn't have to be equal.
             *
             * execution id: On which execution it was set activity id: in which activity was the process instance when setting the variable
             */
            assertThat(historicActivityInstance2.getExecutionId().equals(update2.getExecutionId())).isFalse();
        }
    }

    // Test for ACT-1528, which (correctly) reported that deleting any
    // historic process instance would remove ALL historic variables.
    // Yes. Real serious bug.
    @Deployment
    public void testHistoricProcessInstanceDeleteCascadesCorrectly() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            Map<String, Object> variables = new HashMap<String, Object>();
            variables.put("var1", "value1");
            variables.put("var2", "value2");
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess", variables);
            assertThat(processInstance).isNotNull();

            variables = new HashMap<String, Object>();
            variables.put("var3", "value3");
            variables.put("var4", "value4");
            ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("myProcess", variables);
            assertThat(processInstance2).isNotNull();

            // check variables
            long count = historyService.createHistoricVariableInstanceQuery().count();
            assertThat(count).isEqualTo(4);

            // delete runtime execution of ONE process instance
            runtimeService.deleteProcessInstance(processInstance.getId(), "reason 1");
            historyService.deleteHistoricProcessInstance(processInstance.getId());

            // recheck variables
            // this is a bug: all variables was deleted after delete a history processinstance
            count = historyService.createHistoricVariableInstanceQuery().count();
            assertThat(count).isEqualTo(2);
        }
    }

    @Deployment(resources = "org/activiti/engine/test/history/HistoricVariableInstanceTest.testSimple.bpmn20.xml")
    public void testNativeHistoricVariableInstanceQuery() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            assertThat(managementService.getTableName(HistoricVariableInstance.class)).isEqualTo("ACT_HI_VARINST");
            assertThat(managementService.getTableName(HistoricVariableInstanceEntity.class)).isEqualTo(
                "ACT_HI_VARINST"
            );

            String tableName = managementService.getTableName(HistoricVariableInstance.class);
            String baseQuerySql = "SELECT * FROM " + tableName;

            Map<String, Object> variables = new HashMap<String, Object>();
            variables.put("var1", "value1");
            variables.put("var2", "value2");
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc", variables);
            assertThat(processInstance).isNotNull();

            assertThat(historyService.createNativeHistoricVariableInstanceQuery().sql(baseQuerySql).list()).hasSize(3);

            String sqlWithConditions = baseQuerySql + " where NAME_ = #{name}";
            assertThat(
                historyService
                    .createNativeHistoricVariableInstanceQuery()
                    .sql(sqlWithConditions)
                    .parameter("name", "myVar")
                    .singleResult()
                    .getValue()
            ).isEqualTo("test123");

            sqlWithConditions = baseQuerySql + " where NAME_ like #{name}";
            assertThat(
                historyService
                    .createNativeHistoricVariableInstanceQuery()
                    .sql(sqlWithConditions)
                    .parameter("name", "var%")
                    .list()
            ).hasSize(2);

            // paging
            assertThat(
                historyService.createNativeHistoricVariableInstanceQuery().sql(baseQuerySql).listPage(0, 3)
            ).hasSize(3);
            assertThat(
                historyService.createNativeHistoricVariableInstanceQuery().sql(baseQuerySql).listPage(1, 3)
            ).hasSize(2);
            assertThat(
                historyService
                    .createNativeHistoricVariableInstanceQuery()
                    .sql(sqlWithConditions)
                    .parameter("name", "var%")
                    .listPage(0, 2)
            ).hasSize(2);
        }
    }

    @Deployment(resources = "org/activiti/engine/test/history/HistoricVariableInstanceTest.testSimple.bpmn20.xml")
    public void testNativeHistoricDetailQuery() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            assertThat(managementService.getTableName(HistoricDetail.class)).isEqualTo("ACT_HI_DETAIL");
            assertThat(managementService.getTableName(HistoricVariableUpdate.class)).isEqualTo("ACT_HI_DETAIL");

            String tableName = managementService.getTableName(HistoricDetail.class);
            String baseQuerySql = "SELECT * FROM " + tableName;

            Map<String, Object> variables = new HashMap<String, Object>();
            variables.put("var1", "value1");
            variables.put("var2", "value2");
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc", variables);
            assertThat(processInstance).isNotNull();

            assertThat(historyService.createNativeHistoricDetailQuery().sql(baseQuerySql).list()).hasSize(3);

            String sqlWithConditions = baseQuerySql + " where NAME_ = #{name} and TYPE_ = #{type}";
            assertThat(
                historyService
                    .createNativeHistoricDetailQuery()
                    .sql(sqlWithConditions)
                    .parameter("name", "myVar")
                    .parameter("type", "VariableUpdate")
                    .singleResult()
            ).isNotNull();

            sqlWithConditions = baseQuerySql + " where NAME_ like #{name}";
            assertThat(
                historyService.createNativeHistoricDetailQuery().sql(sqlWithConditions).parameter("name", "var%").list()
            ).hasSize(2);

            // paging
            assertThat(historyService.createNativeHistoricDetailQuery().sql(baseQuerySql).listPage(0, 3)).hasSize(3);
            assertThat(historyService.createNativeHistoricDetailQuery().sql(baseQuerySql).listPage(1, 3)).hasSize(2);
        }
    }

    @Deployment(resources = { "org/activiti/engine/test/history/oneTaskProcess.bpmn20.xml" })
    public void testChangeType() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
            TaskQuery taskQuery = taskService.createTaskQuery();
            Task task = taskQuery.singleResult();
            assertThat(task.getName()).isEqualTo("my task");

            // no type change
            runtimeService.setVariable(processInstance.getId(), "firstVar", "123");
            assertThat(getHistoricVariable("firstVar").getValue()).isEqualTo("123");
            runtimeService.setVariable(processInstance.getId(), "firstVar", "456");
            assertThat(getHistoricVariable("firstVar").getValue()).isEqualTo("456");
            runtimeService.setVariable(processInstance.getId(), "firstVar", "789");
            assertThat(getHistoricVariable("firstVar").getValue()).isEqualTo("789");

            // type is changed from text to integer and back again. same result expected(?)
            runtimeService.setVariable(processInstance.getId(), "secondVar", "123");
            assertThat(getHistoricVariable("secondVar").getValue()).isEqualTo("123");
            runtimeService.setVariable(processInstance.getId(), "secondVar", 456);
            // there are now 2 historic variables, so the following does not work
            assertThat(getHistoricVariable("secondVar").getValue()).isEqualTo(456);
            runtimeService.setVariable(processInstance.getId(), "secondVar", "789");
            // there are now 3 historic variables, so the following does not work
            assertThat(getHistoricVariable("secondVar").getValue()).isEqualTo("789");

            taskService.complete(task.getId());

            assertProcessEnded(processInstance.getId());
        }
    }

    private HistoricVariableInstance getHistoricVariable(String variableName) {
        return historyService.createHistoricVariableInstanceQuery().variableName(variableName).singleResult();
    }

    @Deployment
    public void testRestrictByExecutionId() {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.FULL)) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProc");
            TaskQuery taskQuery = taskService.createTaskQuery();
            Task userTask = taskQuery.singleResult();
            assertThat(userTask.getName()).isEqualTo("userTask1");

            taskService.complete(userTask.getId(), singletonMap("myVar", "test789"));

            assertProcessEnded(processInstance.getId());

            List<HistoricVariableInstance> variables = historyService
                .createHistoricVariableInstanceQuery()
                .executionId(processInstance.getId())
                .list();
            assertThat(variables).hasSize(1);

            HistoricVariableInstanceEntity historicVariable = (HistoricVariableInstanceEntity) variables.get(0);
            assertThat(historicVariable.getTextValue()).isEqualTo("test456");

            assertThat(historyService.createHistoricActivityInstanceQuery().count()).isEqualTo(5);
            assertThat(historyService.createHistoricDetailQuery().count()).isEqualTo(3);
        }
    }
}
